﻿using System;
using CloudDetector.Utils;

namespace CloudDetector.Algorithms
{
    /// <summary>
    /// Árnyék detektáló osztály
    /// </summary>
    public class ShadowDetector
    {
        #region Fileds

        private double          mRasterMapZoomLevel; // a megjelenített kép nagyítottsága
        private LocalVector     mNormVect; // a berajzolt vektor irányvektora
        private double          mVectMagn; // a berajzolt vektor hossza
        private double          mDiagonal;
        private double          mAngle; // (-1, 0) egységvektorral vett szög
        private int             mRowStep;
        private int             mColumnStep;
        private double          mCos;

        #endregion

        #region Constructor
        /// <summary>
        /// Árnyékdetektálás. A megadott felhő - árnyék eltolásvektorból kiszámolja a 
        /// felhők lehetséges helyeit. Feltételezve, hogy a kép összes felhő - árnyék párjára
        /// ez az eltolsvektor érvényes.
        /// </summary>
        /// <param name="zoomlevel">A kép-megjelenítő picturebox nagyítottságának mértéke.</param>
        /// <param name="norm">A rögzített eltolásvektor irányvektora.</param>
        /// <param name="magn">Az eltolásvektor hossza.</param>
        public ShadowDetector(double zoomlevel, LocalVector norm, double magn)
        {
            mRasterMapZoomLevel = zoomlevel;
            mNormVect = norm;
            mVectMagn = magn;
            mDiagonal = magn / zoomlevel;
            mAngle = GetAngle();
            mRowStep = GetSignedRowStep();
            mColumnStep = GetSignedColumnStep();
        }
        #endregion

        #region Util Member Methods

        /// <summary>
        /// Visszaadja a (-1, 0) egységvektor és az eltolásvektor irányvektorának közbezárt szögét.
        /// HEURISZTIKA: (-1, 0) egységvektorral számol, mert EOV-ban az árnyékok a felhőkre relatív 
        /// bal felső képrészletben helyezkednek el.
        /// </summary>
        /// <returns>Közbezárt szög.</returns>
        private double GetAngle()
        {
            // (-1, 0) egységvektor
            LocalVector unitVect = new LocalVector(-1, 0);
            double dotProduct = LocalVector.DotProduct(mNormVect, unitVect);
            mCos = dotProduct / mNormVect.Magnitude() * unitVect.Magnitude();
            return Math.Acos(mCos);
        }

        /// <summary>
        /// Előjeles sorléptetés. Az előjelnek meg kell eggyeznie a normálvektor
        /// második komponensének előjelével.
        /// </summary>
        /// <returns>Előjeles sorléptetés.</returns>
        private int GetSignedRowStep()
        {
            int retNum = (int)Math.Ceiling(Math.Abs(mDiagonal * Math.Sin(mAngle)));
            return mNormVect.Y < 0 ? -retNum : retNum;
        }

        /// <summary>
        /// Előjeles oszlopléptetés. Az előjelnek meg kell eggyeznie a normálvektor
        /// első komponensének előjelével.
        /// </summary>
        /// <returns>Előjeles oszlopléptetés.</returns>
        private int GetSignedColumnStep()
        {
            int retNum = (int)Math.Ceiling(Math.Abs(mDiagonal * mCos));
            return mNormVect.X < 0 ? -retNum : retNum;
        }

        #endregion

        /// <summary>
        /// Árnyékdetektálás. Végigmegyünk a felhő-layeren,és ahol felhő érték van
        /// ahoz képest sorban és oszlopban eltolva felveszünk egy árnyék értéket is.
        /// </summary>
        /// <param name="clouds">Felhő layer</param>
        /// <returns>Felhők és árnyékok layer.</returns>
        public ImageLayers Detect(ImageLayers clouds)
        {
            int LayerCount = clouds.Layercount;
            int H = clouds.Height;
            int W = clouds.Width;
            ImageLayers shadow = new ImageLayers(clouds);

            for (int j = 0; j < H; ++j)
            {
                for (int i = 0; i < W; i++)
                {
                    if ((clouds.layers)[0, i, j] == 255
                        && (i + mColumnStep > 0) && (i + mColumnStep < W)
                        && (j + mRowStep > 0) && (j + mRowStep < H))
                        (shadow.layers)[0, i + mColumnStep, j + mRowStep] = 255;
                }
            }

            return shadow;
        }
    }
}
